package org.acghub.mtdb.common;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import org.acghub.mtdb.core.StorageEngineType;
import org.acghub.mtdb.util.FileOperation;
import org.acghub.mtdb.util.StringUtil;
import org.acghub.mtdb.util.XMLUtil;
import org.w3c.dom.Element;

import java.io.File;
import java.io.PrintStream;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.Objects;

/**
 * @author zhouhe
 */
public final class Config {


    /**
     * 配置文件
     */
    private String inputConf = "classpath:mtdb.xml";
    /**
     * 输出目录
     */
    private String outputDir = "src/main/java";
    /**
     * 包前缀
     */
    private String packagePrefix = "";
    /**
     * 输入编码
     */
    private String inputEncoding = "UTF-8";
    /**
     * 输出编码
     */
    private String outputEncoding = "UTF-8";
    /**
     * bean目录
     */
    private String beanDir = "bean";
    /**
     * table目录
     */
    private String tableDir = "table";

    /**
     * marshal类型, document、binary、json
     */
    private String marshal = "document";

    private String lockeyType = "free_lock";

    private int lockBucketShift = 10;

    // lru、concurrent
    private String tableCacheType = "concurrent";

    private int procedureExecutorShift = 4;

    private boolean verifyUnsafe = true;

    // for init
    private Element element;
    private File outputDirFile;

    private File beanDirFile;
    private File tableDirFile;

    private StorageEngineConfig storageEngineConfig;

    public String getInputConf() {
        return inputConf;
    }

    public void setInputConf(String inputConf) {
        this.inputConf = inputConf;
    }

    public String getOutputDir() {
        return outputDir;
    }

    public void setOutputDir(String outputDir) {
        this.outputDir = outputDir;
    }

    public String getPackagePrefix() {
        return packagePrefix;
    }

    public void setPackagePrefix(String packagePrefix) {
        this.packagePrefix = packagePrefix;
    }

    public String getInputEncoding() {
        return inputEncoding;
    }

    public void setInputEncoding(String inputEncoding) {
        this.inputEncoding = inputEncoding;
    }

    public String getOutputEncoding() {
        return outputEncoding;
    }

    public void setOutputEncoding(String outputEncoding) {
        this.outputEncoding = outputEncoding;
    }

    public void check() {
        String[] ss = this.inputConf.split(":");
        if (ss.length != 2) {
            throw new IllegalArgumentException("Invalid inputConf: " + this.inputConf);
        }
        InputConfPathType inputConfPathType = InputConfPathType.getByType(ss[0]);
        Objects.requireNonNull(inputConfPathType, "Invalid inputConf: " + this.inputConf);

        if (!ss[1].endsWith(".xml")) {
            throw new IllegalArgumentException("Invalid inputConf: " + this.inputConf);
        }
        File file = inputConfPathType.getFile(ss[1]);
        if (!file.exists()) {
            throw new IllegalArgumentException("Invalid inputConf: " + this.inputConf);
        }

        Objects.requireNonNull(Charset.forName(inputEncoding), "Invalid inputEncoding: " + this.inputEncoding);
        Objects.requireNonNull(Charset.forName(outputEncoding), "Invalid outputEncoding: " + this.outputEncoding);
    }

    public String getInputConfContent() {
        String[] ss = this.inputConf.split(":");
        InputConfPathType inputConfPathType = InputConfPathType.getByType(ss[0]);
        return Objects.requireNonNull(inputConfPathType).getContent(ss[1]);
    }

    public File getInputConfFile() {
        String[] ss = this.inputConf.split(":");
        InputConfPathType inputConfPathType = InputConfPathType.getByType(ss[0]);
        return Objects.requireNonNull(inputConfPathType).getFile(ss[1]);
    }

    public String getBeanDir() {
        return beanDir;
    }

    public void setBeanDir(String beanDir) {
        this.beanDir = beanDir;
    }

    public String getTableDir() {
        return tableDir;
    }

    public void setTableDir(String tableDir) {
        this.tableDir = tableDir;
    }

    public Element getElement() {
        return element;
    }

    public File getOutputDirFile() {
        return outputDirFile;
    }

    public File getBeanDirFile() {
        return beanDirFile;
    }

    public File getTableDirFile() {
        return tableDirFile;
    }

    public StorageEngineConfig getStorageEngineConfig() {
        return storageEngineConfig;
    }

    public void setStorageEngineConfig(StorageEngineConfig storageEngineConfig) {
        this.storageEngineConfig = storageEngineConfig;
    }


    /**
     * 初始化
     *
     * @throws Exception
     */
    public void init() throws Exception {
        this.check();
        // 1.解析xml
        element = XMLUtil.getRootElement(this.getInputConfFile());
        Objects.requireNonNull(element, "解析xml错误，path:" + this.getInputConf());
    }

    public void make() {
        StringBuilder sb = new StringBuilder(this.outputDir);
        if (StringUtil.isNotEmpty(this.packagePrefix)) {
            sb.append(File.separator).append(this.packagePrefix.replace(".", File.separator));
        }
        this.outputDirFile = new File(sb.toString());
        this.beanDirFile = new File(this.outputDirFile, this.beanDir);
        this.tableDirFile = new File(this.outputDirFile, this.tableDir);
        System.err.println("================" + " 输出目录: " + this.outputDirFile.getAbsolutePath() + " " + "================");
    }

    public PrintStream openBeanFile(String classname) {
        return FileOperation.fopen(beanDirFile, classname + ".java", outputEncoding);
    }

    public PrintStream openTableFile(String classname) {
        return FileOperation.fopen(tableDirFile, classname + ".java", outputEncoding);
    }

    public String fullPackage(String suffix) {
        if (StringUtil.isEmpty(suffix)) {
            throw new IllegalArgumentException("Invalid suffix: " + suffix);
        }
        String packageName = this.packagePrefix;
        if (StringUtil.isNotEmpty(packageName) && !packageName.endsWith(".")) {
            packageName += ".";
        }
        while (StringUtil.isNotEmpty(suffix) && suffix.startsWith(".")) {
            suffix = suffix.substring(1);
        }
        if (StringUtil.isEmpty(suffix)) {
            throw new IllegalArgumentException("Invalid suffix: " + suffix);
        }
        return StringUtil.concat(packageName, suffix);
    }

    public String beanFullPackage() {
        return this.fullPackage(beanDir);
    }

    public String tableFullPackage() {
        return this.fullPackage(tableDir);
    }

    public MarshalType getMarshalType() {
        return MarshalType.getByType(this.marshal);
    }

    public void setMarshal(String marshal) {
        this.marshal = marshal;
    }

    public void setLockeyType(String lockeyType) {
        this.lockeyType = lockeyType;
    }

    public String getLockeyType() {
        return lockeyType;
    }

    public void setLockBucketShift(int lockBucketShift) {
        this.lockBucketShift = lockBucketShift;
    }

    public int getLockBucketShift() {
        return lockBucketShift;
    }

    public void setTableCacheType(String tableCacheType) {
        this.tableCacheType = tableCacheType;
    }

    public String getTableCacheType() {
        return tableCacheType;
    }

    public int getProcedureExecutorShift() {
        return procedureExecutorShift;
    }

    public void setProcedureExecutorShift(int procedureExecutorShift) {
        this.procedureExecutorShift = procedureExecutorShift;
    }

    public void setVerifyUnsafe(boolean verifyUnsafe) {
        this.verifyUnsafe = verifyUnsafe;
    }

    public boolean isVerifyUnsafe() {
        return verifyUnsafe;
    }

    public enum InputConfPathType {
        CLASS_PATH("classpath") {
            @Override
            public String getContent(String path) {
                return ResourceUtil.readUtf8Str(path);
            }

            @Override
            public File getFile(String path) {
                return FileUtil.file(ResourceUtil.getResource(path));
            }
        },

        ABSOLUTE("absolute") {
            @Override
            public String getContent(String path) {
                return FileUtil.readUtf8String(path);
            }

            @Override
            public File getFile(String path) {
                return FileUtil.file(path);
            }
        };
        private final String type;

        InputConfPathType(String type) {
            this.type = type;
        }

        public static InputConfPathType getByType(String type) {
            for (InputConfPathType inputConfPathType : InputConfPathType.values()) {
                if (inputConfPathType.getType().equals(type)) {
                    return inputConfPathType;
                }
            }
            return null;
        }

        public String getType() {
            return type;
        }

        public abstract String getContent(String path);

        public abstract File getFile(String path);
    }

    public enum MarshalType {
        DOCUMENT("document"),

        BINARY("byte"),

        JSON("json"),

        ;

        private final String type;

        MarshalType(String type) {
            this.type = type;
        }

        public static MarshalType getByType(String type) {
            for (MarshalType marshalType : MarshalType.values()) {
                if (marshalType.getType().equals(type)) {
                    return marshalType;
                }
            }
            return null;
        }

        public String getType() {
            return type;
        }
    }

    /**
     * @author zhouhe
     */
    public static class StorageEngineConfig {
        private String name;
        private String type;
        private Map<Object, Object> properties;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getType() {
            return type;
        }

        public void setType(String type) {
            this.type = type;
        }

        public Map<Object, Object> getProperties() {
            return properties;
        }

        public void setProperties(Map<Object, Object> properties) {
            this.properties = properties;
        }

        public StorageEngineType getStorageEngineType() {
            return StorageEngineType.of(type);
        }

        public String getStr(Object key, String defaultValue) {
            return (String) properties.getOrDefault(key, defaultValue);
        }

        public int getInt(Object key, int defaultValue) {
            return (Integer) properties.getOrDefault(key, defaultValue);
        }

        public long getLong(Object key, long defaultValue) {
            return (Long) properties.getOrDefault(key, defaultValue);
        }

        public boolean getBoolean(Object key, boolean defaultValue) {
            return (Boolean) properties.getOrDefault(key, defaultValue);
        }

        @Override
        public String toString() {
            return "StorageEngineConfig{" +
                    "name='" + name + '\'' +
                    ", type='" + type + '\'' +
                    ", properties=" + properties +
                    '}';
        }
    }
}
